- Published on
ShapeStyle In SwiftUI
- Authors
- Name
ShapeStyle 是什么
在 SwiftUI 中我们会经常在使用一些 ViewModifier 的时候,比如 .border
的时候。会传递一个参数,这个参数的类型是Shape
。其中,.border
的完整定义如下:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension View {
/// Layers a secondary view in front of this view.
///
/// When you apply an overlay to a view, the original view continues to
/// provide the layout characteristics for the resulting view. In the
/// following example, the heart image is shown overlaid in front of, and
/// aligned to the bottom of the folder image.
///
/// Image(systemName: "folder")
/// .font(.system(size: 55, weight: .thin))
/// .overlay(Text("❤️"), alignment: .bottom)
///
/// 
///
/// - Parameters:
/// - overlay: The view to layer in front of this view.
/// - alignment: The alignment for `overlay` in relation to this view.
///
/// - Returns: A view that layers `overlay` in front of the view.
@available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Use `overlay(alignment:content:)` instead.")
@available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Use `overlay(alignment:content:)` instead.")
@available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Use `overlay(alignment:content:)` instead.")
@available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Use `overlay(alignment:content:)` instead.")
@available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "Use `overlay(alignment:content:)` instead.")
@inlinable nonisolated public func overlay<Overlay>(_ overlay: Overlay, alignment: Alignment = .center) -> some View where Overlay : View
/// Adds a border to this view with the specified style and width.
///
/// Use this modifier to draw a border of a specified width around the
/// view's frame. By default, the border appears inside the bounds of this
/// view. For example, you can add a four-point wide border covers the text:
///
/// Text("Purple border inside the view bounds.")
/// .border(Color.purple, width: 4)
///
/// 
///
/// To place a border around the outside of this view, apply padding of the
/// same width before adding the border:
///
/// Text("Purple border outside the view bounds.")
/// .padding(4)
/// .border(Color.purple, width: 4)
///
/// 
///
/// - Parameters:
/// - content: A value that conforms to the ``ShapeStyle`` protocol,
/// like a ``Color`` or ``HierarchicalShapeStyle``, that SwiftUI
/// uses to fill the border.
/// - width: The thickness of the border. The default is 1 pixel.
///
/// - Returns: A view that adds a border with the specified style and width
/// to this view.
@inlinable nonisolated public func border<S>(_ content: S, width: CGFloat = 1) -> some View where S : ShapeStyle
}
border可以用在哪些类型上面
我们可以发现这个方法是定义在 extension View
中,那么是不是所有的满足 View
协议的类型都可以调用这个方法呢?
答案是肯定的
对协议的扩展添加限制的两种情况
这里的 extension View
加上限制条件。常见的限制条件有两种情况:
- Self类型约束
- where限定条件
比如下面的代码示例,针对上面的两种情况的举例:
Self限定
protocol Animal {}
extension Animal where Self: Equatable {
func isSame(as other: Self) -> Bool {
return self == other
}
}
struct Dog: Animal, Equatable {
let name: String
}
struct Cat: Animal {
let name: String
}
let d1 = Dog(name: "旺财")
let d2 = Dog(name: "旺财")
print(d1.isSame(as: d2)) // ✅ 可以用,因为 Dog: Equatable
let c1 = Cat(name: "小花")
// c1.isSame(as: c1) ❌ 编译报错,因为 Cat 没有实现 Equatable
对关联类型加以限制
protocol Container {
associatedtype Item
var items: [Item] { get }
}
extension Container where Item == Int {
func sum() -> Int {
items.reduce(0, +)
}
}
struct IntBox: Container {
var items: [Int]
}
struct StringBox: Container {
var items: [String]
}
let box = IntBox(items: [1, 2, 3])
print(box.sum()) // ✅ 可以
let sbox = StringBox(items: ["a", "b"])
// sbox.sum() ❌ 不行,因为 Item 不是 Int
对其定义的语法分析
@inlinable nonisolated public func border<S>(_ content: S, width: CGFloat = 1) -> some View where S : ShapeStyle
在 SwiftUI 中,@inlinable 的主要作用是 让常用 modifier 方法在跨模块调用时也能被内联,从而提升性能,减少开销。
nonisolated
在 Swift Concurrency 中 Actor 内部的方法和属性是隔离的 (isolated),外部想要访问这些方法和属性,需要使用 await 异步调用,保证其线程安全。
有些时候,我们希望某些 不依赖 actor 内部状态的方法 不需要异步/不受 actor 隔离,这时,我们可以在方法前面加上 nonisolated
actor Counter {
private var value = 0
// 普通方法 -> 需要 await
func increment() { value += 1 }
// 不依赖内部状态 -> 可以 nonisolated
nonisolated func version() -> String {
"1.0"
}
}
let c = Counter()
await c.increment() // 需要 await
print(c.version()) // 不需要 await,直接同步调用
SwiftUI 中的 nonisolated
为什么要 nonisolated? SwiftUI 的 View 并不是 actor,但 Swift 现在在很多地方支持 Sendable / actor 安全推导。 标记 nonisolated 是为了告诉编译器: 这个方法是纯粹的修饰器,不会依赖并发上下文,也不访问任何 actor 隔离的状态。 这样你就可以在任何并发环境里自由调用 .border(...),不需要 await。
泛型和泛型约束
我们希望这个方法中的参数类型是一个符合
如果是让我们来定义这个 ViewModifier, 我大概会这样定义:
@inlinable nonisolated public func border(content: Content, width: CGFloat) -> some View {
}
这里,我们需要对边框的内容填充增加限定。其中的 Content 我们可以用泛型思想。
@inlinable nonisolated public func border<S>(_ content:S, width: CGFloat = 1) -> some View where S : ShapeStyle {
}
通过在方法名后面加上<S>
来定义一个泛型,这个泛型是用来约定我们参数 content 的类型,在方法后面采用 S: ShapeStyle 来限定其类型S必须遵循 ShapeStyle
这里我们需要回去复习一下 TSLP 中相关的内容:
- 方法的参数标签
- 参数的默认值和可选参数 https://docs.swift.org/swift-book/documentation/the-swift-programming-language/functions
- some View 是什么类型 Opaque Type
- where 的用法和作用 Generic where